﻿namespace StockSharp.Algo.Indicators.Trend
{
	using System;
	using System.Collections.Generic;

	using StockSharp.Algo.Candles;

	///<summary>
	/// ЗигЗаг (Metastock).
	///</summary>
	/// <remarks>
	/// Индикатор Зиг-Заг (Zig Zag) фильтрует колебания цен или значений индикаторов, которые не выходят за определенную величину
	/// выраженную в % или абсолютных числах. Это делается для предварительного анализа графика на котором акцентированы только 
	/// достаточно большие изменения цен (значений индикатора).
	/// </remarks>
	public class ZigZagEquis : BaseIndicator<ShiftedIndicatorValue>
	{
		private readonly IList<decimal> _buffer = new List<decimal>();
		private readonly List<decimal> _zigZagBuffer = new List<decimal>();

		/// <summary>
		/// Создать <see cref="ZigZagEquis"/>.
		/// </summary>
		public ZigZagEquis()
		{
		}

		private decimal _deviation = 0.45m * 0.01m;

		///<summary>
		/// Процент изменения.
		///</summary>
		/// <remarks>Указывается в диапазоне от 0 до 1</remarks>
		public decimal Deviation
		{
			get { return _deviation; }
			set
			{
				if (value == 0)
					throw new ArgumentOutOfRangeException("value");

				_deviation = value;
			}
		}

		private Func<Candle, decimal> _byPrice = candle => candle.ClosePrice;

		///<summary>
		/// Конвертер, который возвращает из свечки цену для расчетов.
		///</summary>
		public Func<Candle, decimal> ByPrice
		{
			get { return _byPrice; }
			set { _byPrice = value; }
		}

		///<summary>
		/// Текущее значение индикатора.
		///</summary>
		public decimal CurrentValue { get; private set; }

		/// <summary>
		/// Сбросить состояние индикатора на первоначальное. Метод вызывается каждый раз, когда меняются первоначальные настройки (например, длина периода).
		/// </summary>
		public override void Reset()
		{
			_buffer.Clear();
			_zigZagBuffer.Clear();
			base.Reset();
		}

		/// <summary>
		/// Обработать входное значение.
		/// </summary>
		/// <param name="input">Входное значение.</param>
		/// <returns>Результирующее значение.</returns>
		protected override ShiftedIndicatorValue OnProcess(IIndicatorValue input)
		{
			_buffer.Add(_byPrice(input.GetValue<Candle>()));
			_zigZagBuffer.Add(0);

			const int level = 3;
			int limit = 0, count = 0;
			while (count < level && limit >= 0)
			{
				var res = _zigZagBuffer[limit];
				if (res != 0)
				{
					count++;
				}
				limit--;
			}
			limit++;

			decimal min = _buffer[limit];
			decimal max = min;
			int action = 0, j = 0;
			for (int i = limit + 1; i < _buffer.Count; i++)
			{
				if (_buffer[i] > max)
				{
					max = _buffer[i];
					if (action != 2) //action=1:building the down-point (min) of ZigZag
					{
						if (max - min >= _deviation * min) //min (action!=2) end,max (action=2) begin
						{
							action = 2;
							_zigZagBuffer[i] = max;
							j = i;
							min = max;
						}
						else
							_zigZagBuffer[i] = 0.0m; //max-min=miser,(action!=2) continue
					}
					else //max (action=2) continue
					{
						_zigZagBuffer[j] = 0.0m;
						_zigZagBuffer[i] = max;
						j = i;
						min = max;
					}
				}
				else if (_buffer[i] < min)
				{
					min = _buffer[i];
					if (action != 1) //action=2:building the up-point (max) of ZigZag
					{
						if (max - min >= _deviation * max) //max (action!=1) end,min (action=1) begin
						{
							action = 1;
							_zigZagBuffer[i] = min;
							j = i;
							max = min;
						}
						else
							_zigZagBuffer[i] = 0.0m; //max-min=miser,(action!=1) continue
					}
					else //min (action=1) continue
					{
						_zigZagBuffer[j] = 0.0m;
						_zigZagBuffer[i] = min;
						j = i;
						max = min;
					}
				}
				else
					_zigZagBuffer[i] = 0.0m;
			}

			int valuesCount = 0, valueId = 0;
			decimal last = 0, lastButOne = 0;
			for (int i = _zigZagBuffer.Count - 1; i > 0 && valuesCount < 2; i--, valueId++)
			{
				if (_zigZagBuffer[i] != 0)
				{
					valuesCount++;

					if (valuesCount == 1)
						last = _zigZagBuffer[i];
					else
						lastButOne = _zigZagBuffer[i];
				}
			}

			if (valuesCount == 2)
			{
				IsFormed = true;
				CurrentValue = last;

				return new ShiftedIndicatorValue(valueId - 1, input.SetValue(lastButOne));
			}

			return Container.Count > 1 ? this.GetCurrentValue() : new ShiftedIndicatorValue();
		}
	}
}